package org.dicr.sys.linux;

import java.io.*;
import java.lang.ref.*;
import java.util.*;
import java.util.logging.*;

import org.dicr.mng.exc.*;
import org.dicr.util.data.*;

/**
 * Датчик дисков.
 * <P>
 * Используется информация команды '<CODE>df</CODE>'. Данные кэшируются в
 * течении <CODE>cacheTime</CODE> времени.
 * </P>
 * 
 * @author Igor A Tarasov, &lt;java@dicr.org&gt;
 * @version 1.0
 * @see DiskInfoData
 */
public class DiskInfo {

	/**
	 * Логгер.
	 */
	protected static final Logger log = Logger.getLogger(DiskInfo.class.getPackage().getName());

	/**
	 * Команда информации о дисках.
	 */
	public static final String CMD_DF = "/bin/df";

	/**
	 * Время кэширования.
	 */
	protected static long cacheTime = 5000;

	/**
	 * Данные дисков.
	 */
	protected static SoftReference<DiskInfoData[]> dataRef = null;

	/**
	 * Время данных.
	 */
	protected static long dataTime = 0;

	/**
	 * Возвращает время кэширования.
	 * 
	 * @return время в миллисекундах.
	 */
	public static synchronized long getCacheTime() {
		return DiskInfo.cacheTime;
	}

	/**
	 * Устанавливает время кэширования.
	 * 
	 * @param millisec
	 *            время в миллисекундах ).
	 */
	public static synchronized void setCacheTime(final long millisec) {
		if (millisec < 0) throw new IllegalArgumentException("negative cache period");
		DiskInfo.cacheTime = millisec;
		DiskInfo.log.config("configured cacheTime=" + DiskInfo.cacheTime);
	}

	/**
	 * Преобразует список в строку.
	 * 
	 * @param command
	 *            список элементов команды.
	 * @return строка команды.
	 */
	protected static String listToString(final List<String> command) {
		assert command != null;
		final StringBuilder sb = new StringBuilder();
		for (final String s : command) {
			sb.append(s);
			sb.append(' ');
		}
		sb.deleteCharAt(sb.length() - 1);
		return sb.toString();
	}

	/**
	 * Возвращает информацию о дисках.
	 * 
	 * @return информация о дисках.
	 * @throws CommandException
	 *             ошибка запуска команды.
	 */
	public static synchronized DiskInfoData[] getInfo() throws CommandException {
		DiskInfoData[] data = null;
		if (DiskInfo.dataRef != null) {
			data = DiskInfo.dataRef.get();
		}
		if (data != null && System.currentTimeMillis() - DiskInfo.dataTime < DiskInfo.cacheTime) return data;
		BufferedReader in = null;
		final ArrayList<String> command = new ArrayList<>();
		try {
			command.add(DiskInfo.CMD_DF);
			command.add("--all");
			command.add("--block-size=1K");
			command.add("--local");
			command.add("--portability");
			command.add("--sync");
			final ProcessBuilder pb = new ProcessBuilder(command);
			pb.redirectErrorStream(true);
			DiskInfo.log.finest("running: " + DiskInfo.listToString(command));
			final Process p = pb.start();
			in = new BufferedReader(new InputStreamReader(p.getInputStream()));
			final ArrayList<String> out = new ArrayList<>();
			while (true) {
				final String s = in.readLine();
				if (s == null) {
					break;
				}
				out.add(s);
			}
			in.close();
			in = null;
			final int ret = p.waitFor();
			if (ret != 0) throw new CommandException(command, DiskInfo.listToString(out));
			final ArrayList<DiskInfoData> elements = new ArrayList<>();
			for (String line : out) {
				line = line.trim();
				if (!line.startsWith("/dev")) {
					continue;
				}
				final String[] tokens = line.split("\\s+");
				if (tokens.length < 6) {
					DiskInfo.log.warning("error parsing line '" + line + "'");
					continue;
				}
				try {
					final String partition = StringUtils.normalize(tokens[0]);
					final String mountPoint = StringUtils.normalize(tokens[5]);
					final long size = Long.parseLong(tokens[1]) * 1024;
					final long free = Long.parseLong(tokens[3]) * 1024;
					elements.add(new DiskInfoData(partition, mountPoint, size, free));
				} catch (final NumberFormatException ex) {
					DiskInfo.log.log(Level.WARNING, "error parsing line '" + line + "'", ex);
					continue;
				}
			}
			data = elements.toArray(new DiskInfoData[0]);
			DiskInfo.dataTime = System.currentTimeMillis();
			DiskInfo.dataRef = new SoftReference<>(data);
		} catch (final IOException ex) {
			throw new CommandException(command, ex);
		} catch (final InterruptedException ex) {
			throw new CommandException(command, ex);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (final Exception ex) {
					ex.printStackTrace();
				}
			}
		}
		return data;
	}
}
